#region Namespaces

using System;
using System.Runtime.Serialization;
using System.Xml;

#endregion

namespace Vibstudio.X4NET.Xml.Linq
{
    [Serializable]
    public sealed class XName : IEquatable<XName>, ISerializable
    {
        private readonly string _local;

        private readonly XNamespace _ns;

        private XName(SerializationInfo info, StreamingContext context)
        {
            string expandedName = info.GetString("name");
            string local, ns;
            ExpandName(expandedName, out local, out ns);
            _local = local;
            _ns = XNamespace.Get(ns);
        }

        internal XName(string local, XNamespace ns)
        {
            _local = XmlConvert.VerifyNCName(local);
            _ns = ns;
        }

        public string LocalName
        {
            get { return _local; }
        }

        public XNamespace Namespace
        {
            get { return _ns; }
        }

        public string NamespaceName
        {
            get { return _ns.NamespaceName; }
        }

        bool IEquatable<XName>.Equals(XName other)
        {
            return this == other;
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
            {
                throw new ArgumentNullException("info");
            }

            info.AddValue("name", ToString());
        }

        private static Exception ErrorInvalidExpandedName()
        {
            return new ArgumentException("Invalid expanded name.");
        }

        public override bool Equals(object obj)
        {
            XName n = obj as XName;
            return n != null && this == n;
        }

        public static XName Get(string expandedName)
        {
            string local, ns;
            ExpandName(expandedName, out local, out ns);
            return Get(local, ns);
        }

        private static void ExpandName(string expandedName, out string local, out string ns)
        {
            if (expandedName == null)
            {
                throw new ArgumentNullException("expandedName");
            }
            ns = null;
            local = null;
            if (expandedName.Length == 0)
            {
                throw ErrorInvalidExpandedName();
            }

            if (expandedName[0] == '{')
            {
                for (int i = 1; i < expandedName.Length; i++)
                {
                    if (expandedName[i] == '}')
                    {
                        ns = expandedName.Substring(1, i - 1);
                    }
                }
                if (String.IsNullOrEmpty(ns))
                {
                    throw ErrorInvalidExpandedName();
                }
                if (expandedName.Length == ns.Length + 2)
                {
                    throw ErrorInvalidExpandedName();
                }
                local = expandedName.Substring(ns.Length + 2);
            }
            else
            {
                local = expandedName;
                ns = String.Empty;
            }
        }

        public static XName Get(string localName, string namespaceName)
        {
            return XNamespace.Get(namespaceName).GetName(localName);
        }

        public override int GetHashCode()
        {
            return _local.GetHashCode() ^ _ns.GetHashCode();
        }

        public static bool operator ==(XName left, XName right)
        {
            if ((object)left == null)
            {
                return (object)right == null;
            }

            if ((object)right == null)
            {
                return false;
            }

            return ReferenceEquals(left, right) || left._local == right._local && left._ns == right._ns;
        }

        [CLSCompliant(false)]
        public static implicit operator XName(string expandedName)
        {
            return expandedName == null ? null : Get(expandedName);
        }

        public static bool operator !=(XName left, XName right)
        {
            return !(left == right);
        }

        public override string ToString()
        {
            return ReferenceEquals(_ns, XNamespace.None) ? _local : String.Concat("{", _ns.NamespaceName, "}", _local);
        }
    }
}